/*
 * registry.cc
 *
 *  Created on: Sep 19, 2012
 *      Author: shaohong
 */


#include "registry.hh"
#include <numeric>
#include <algorithm>
#include <typeinfo>
#include <iostream>

using namespace std;

/*
Two entries are equal if the course ids and grades of the two entries are the same.
*/
bool isSameEntry(const Entry& e1, const Entry& e2) {
	if (0 == e1.course.courseId.compare(e2.course.courseId)) {
		if (e1.grade == e2.grade) {
			return true;
		}
	}

	return false;
}

bool isSameCourse(const Entry& e1, const Entry& e2) {

	return (e1.course.courseId.compare(e2.course.courseId) == 0);
}


void removeEntryFromTranscripts(vector<Entry>& transcripts, const Entry & entry2bRemoved) {
	for ( vector<Entry>::iterator itr = transcripts.begin();
			itr != transcripts.end(); itr ++) {
		Entry & currEntry = (*itr);

		if (isSameEntry(currEntry, entry2bRemoved)) {
			transcripts.erase(itr);
			return;
		}

	}
}

void updateTranscripts(vector<Entry>& transcripts, const Entry & entry) {
	for(size_t i=0; i<transcripts.size(); i++) {
		if (isSameCourse(transcripts[i], entry) ) {
			if (entry.grade > transcripts[i].grade) {
				// override the old grade with the better grade
				transcripts[i] = entry;
			} else {
				// do nothing!
			}
			return;
		}
	}

	// add this new entry to this transcripts
	transcripts.push_back(entry);
}


void addEntry(std::map<std::string, std::vector<Entry>>& registry, const std::string& studentId, const Entry& entry) {
//	If the student id doesn't exist in the registry
//	it should be added there along with the entry.
//	 *
//	 * If the student already has an entry for the same course, the entry
//	 * with the better grade should be chosen to the registry. The entry
//	 * should replace the existing one, and thus be in the same place as
//	 * the old entry in the "transcript".

	map<string, vector<Entry>>::iterator itr = registry.find(studentId);
	if (itr == registry.end()) {
		// no course entries for the given studentId
		vector<Entry> transcripts;
		transcripts.push_back(entry);
		registry.insert(make_pair(studentId, transcripts));
		return;

	} else {
		// transcripts already exits, let's see if we need to update it
		vector<Entry>& transcripts = itr->second;

//		cout << typeid(itr->second).name() << endl;

		updateTranscripts(transcripts, entry);
	}

}


//* Removes the parameter entry from the parameter student. If there is no such
// * entry for the student or no such student the function does nothing.
void removeEntry(std::map<std::string, std::vector<Entry>>& registry, const std::string& studentId, const Entry& entry) {
	map<string, vector<Entry>>::iterator studentRecord = registry.find(studentId);
	if (studentRecord == registry.end()) {
		return;
	}

	vector<Entry>& transcripts = studentRecord->second;
	removeEntryFromTranscripts(transcripts, entry);
}


void printTranscript(const std::map<std::string, std::vector<Entry>>& registry, const std::string& studentId) {
	map<string, vector<Entry>>::const_iterator studentRecord = registry.find(studentId);
	if (studentRecord == registry.end()) {
		cout << "No student with id " << studentId << " was found in the registry." << endl;
		return;
	}

	//	 * Transcript for <student id>:
	//	 * <course id> <course name> <course credits> <grade>

	cout << "Transcript for " << studentId << ":" << endl;

	vector<Entry> transcripts = studentRecord->second;
	for ( vector<Entry>::iterator itr = transcripts.begin();
			itr != transcripts.end(); itr ++) {
		Entry & currEntry = (*itr);
		cout << currEntry.course.courseId << " " << currEntry.course.courseName << " " << currEntry.course.credits << " " << currEntry.grade << endl;
	}

}

bool isFailed(const Entry & entry) {
	return (entry.grade == 0);
}

std::vector<Entry> getFailed(const std::map<std::string, std::vector<Entry>>& registry) {

	vector<Entry> failedOnes;
	back_insert_iterator<vector<Entry>> failedRecordsInserter(failedOnes);

	map<std::string, std::vector<Entry>>::const_iterator studentRecord = registry.begin();
	while (studentRecord != registry.end()) {
		const vector<Entry>& transcripts = studentRecord->second;
		copy_if(transcripts.begin(), transcripts.end(), failedRecordsInserter, isFailed);

		studentRecord ++;
	}

	return failedOnes;
}

size_t addCredits (size_t existing, const Entry& entry2) {
	return existing + entry2.course.credits;
}

double addGradeTimesCredits(double existing, const Entry& entry){
	return existing + entry.course.credits * entry.grade;
}

size_t calculateTotalCredits(const std::map<std::string, std::vector<Entry>>& registry, const std::string& studentId) {
	map<string, vector<Entry>>::const_iterator studentRecord = registry.find(studentId);

	if (studentRecord == registry.end()) {
		return 0;
	}

	const vector<Entry>& transcripts = studentRecord->second;

	//accumulate
	return accumulate(transcripts.begin(), transcripts.end(), 0, addCredits);

}


double calculateAverage(const std::map<std::string, std::vector<Entry>>& registry, const std::string& studentId) {
	map<string, vector<Entry>>::const_iterator studentRecord = registry.find(studentId);

	if (studentRecord == registry.end()) {
		return 0;
	}

	const vector<Entry>& transcripts = studentRecord->second;

	size_t totalCredits = calculateTotalCredits(registry, studentId);
	if (totalCredits == 0) {
		return 0;
	}
	double totalGrade = accumulate(transcripts.begin(), transcripts.end(), 0, addGradeTimesCredits);
	return totalGrade/totalCredits;
}


